package com.vmj.heap;

import com.vmj.rtda.Frame;
import com.vmj.rtda.Thread;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by yangshoujun on 2017/8/26 下午12:13.
 */
public class VMClass {

    private int accessFlags;
    private String name;
    private String sourceFile;
    private String superClassName;
    private List<String> interfaceNames;
    private RuntimeConstantPool runtimeConstantPool;
    private com.vmj.classfile.entity.ConstantPool cp;
    private List<Field> fields;
    private List<Method> methods;
    private ClassLoader classLoader;
    private VMClass superClass;
    private List<VMClass> interfaces;
    private int instanceSlotCount;
    private int staticSlotCount;
    private Slots staticVars;
    private boolean initStarted;
    private VMObject jClass;

    public VMClass getArrayClass() {
        String arrayClassName = ClassUtils.getArrayClassName(getName());
        return getClassLoader().loadClass(arrayClassName);
    }

    public VMObject instanciate() {
        return newObject();
    }

    public Method getMainMethod() {
        return getStaticMethod("main", "([Ljava/lang/String;)V");
    }

    public Method getStaticMethod(String name, String descriptor) {
        return getMethod(name, descriptor,true);
    }

    public Method getInstanceMethod(String name, String descriptor) {
        return getMethod(name, descriptor,false);
    }

    public Method getMethod(String name, String descriptor, boolean isStatic) {
        for(Method method : getMethods()) {
            if( (method.isStatic() == isStatic) && name.equals(method.getName()) && descriptor.equals(method.getDescriptor())) {
                return method;
            }
        }

        return null;
    }

    public VMObject newObject() {
        return new VMObject(this);
    }

    public boolean isAccessibleTo(VMClass other) {
        return this.isPublic() || this.getPackageName().equals(other.getPackageName());
    }

    public String getPackageName() {
        int index = this.name.lastIndexOf(File.separator);
        if(index > 0) {
            return this.name.substring(0, index);
        }
        return null;
    }

    public boolean isSubClassOf(VMClass other) {
        for(VMClass cls = this.getSuperClass(); cls != null; cls = cls.getSuperClass()) {
            if(cls != null && cls.equals(other)) {
                return true;
            }
        }

        return false;
    }

    public static void initClass(Thread thread, VMClass clazz) {
        clazz.setInitStarted(true);
        schuduleClinit(thread, clazz);
        initSuperClass(thread, clazz);
    }

    public static void schuduleClinit(Thread thread, VMClass clazz) {
        Method clInit = clazz.getClInitMethod();
        if(clInit != null) {
            Frame frame = new Frame(thread, clInit);
            thread.pushFrame(frame);
        }
    }

    public Method getClInitMethod() {
        return getMethod("<clinit>", "()V", true);
    }

    public static void initSuperClass(Thread thread, VMClass clazz) {
        if(!clazz.isInterface()) {
            VMClass superClass = clazz.getSuperClass();
            if(superClass != null && !superClass.isInitStarted()) {
                initClass(thread, superClass);
            }
        }
    }

    public boolean isImplements(VMClass iface) {
        for(VMClass clz = this; clz != null; clz = clz.getSuperClass()) {
            List<VMClass> ifaces = clz.getInterfaces();
            for(VMClass ifas : ifaces) {
                if(iface.equals(ifas)) {
                    return true;
                }
            }
        }
        return false;
    }

    public VMClass getComponentClass() {
        String componentClassName = getComponentClassName();
        return classLoader.loadClass(componentClassName);
    }

    private String getComponentClassName() {
        if(name.charAt(0) == '[') {
            String componentTypeDescriptor = name.substring(1);
            return ClassUtils.toClassName(componentTypeDescriptor);
        }

        throw new RuntimeException("Not array class: " + name);
    }

    public Field getField(String name, String descriptor, boolean isStatic) {
        for(VMClass cls = this; cls != null; cls = cls.getSuperClass()) {
            for(Field field : cls.getFields()) {
                if(field.isStatic() == isStatic && name.equals(field.getName()) && descriptor.equals(field.getDescriptor())) {
                    return field;
                }
            }
        }
        return null;
    }

    public List<Field> getField(boolean publicOnly) {
        List<Field> fields = new ArrayList<Field>();
        if(publicOnly) {
            for(VMClass cls = this; cls != null; cls = cls.getSuperClass()) {
                for(Field field : cls.getFields()) {
                    if(field.isPublic()) {
                        fields.add(field);
                    }
                }
            }
            return fields;
        } else {
            return getFields();
        }
    }

    public VMObject getRefVar(String fieldName, String fieldDescriptor) {
        Field field = getField(fieldName, fieldDescriptor, true);
        return staticVars.getRef(field.getSlotId());
    }

    public void setRefVar(String fieldName, String fieldDescriptor, VMObject ref) {
        Field field = getField(fieldName, fieldDescriptor, true);
        staticVars.setRef(field.getSlotId(), ref);
    }

    public Method getConstructor(String descriptor) {
        return getInstanceMethod("<init>", descriptor);
    }

    public List<Method> getConstructor(boolean publicOnly) {
        List<Method> constructors = new ArrayList<Method>();
        for(Method method : getMethods()) {
            if(method.isConstructor()) {
                if(!publicOnly || method.isPublic()) {
                    constructors.add(method);
                }
            }
        }
        return constructors;
    }

    public String getJavaName() {
        return name.replaceAll("/", ".");
    }

    public boolean isArray() {
        return this.name.charAt(0) == '[';
    }

    public boolean isSuperClassOf(VMClass other) {
        return other.isSubClassOf(this);
    }

    public int getAccessFlags() {
        return accessFlags;
    }

    public boolean isPublic() {
        return 0 != (this.accessFlags & AccessFlag.ACC_PUBLIC);
    }

    public boolean isFinal() {
        return 0 != (this.accessFlags & AccessFlag.ACC_FINAL);
    }

    public boolean isSuper() {
        return 0 != (this.accessFlags & AccessFlag.ACC_SUPER);
    }

    public boolean isInterface() {
        return 0 != (this.accessFlags & AccessFlag.ACC_INTERFACE);
    }

    public boolean isPrimitive() {
        return ClassUtils.primitiveMap.containsKey(name);
    }

    public boolean isAbstract() {
        return 0 != (this.accessFlags & AccessFlag.ACC_ABSTRACT);
    }

    public boolean isSynthetic() {
        return 0 != (this.accessFlags & AccessFlag.ACC_SYNTHETIC);
    }

    public boolean isAnnotation() {
        return 0 != (this.accessFlags & AccessFlag.ACC_ANNOTATION);
    }

    public boolean isEnum() {
        return 0 != (this.accessFlags & AccessFlag.ACC_ENUM);
    }

    public boolean isJlObject() {
        return name.equals("java/lang/Object");
    }
    public boolean isJlCloneable() {
        return name.equals("java/lang/Cloneable");
    }
    public boolean isJioSerializable() {
        return name.equals("java/io/Serializable");
    }

    public boolean isSubInterfaceOf(VMClass iface) {
        for(VMClass superInterface : getInterfaces()) {
            if(superInterface.equals(iface) || superInterface.isSubInterfaceOf(iface)) {
                return true;
            }
        }
        return false;
    }

    public boolean isSuperInterfaceOf(VMClass iface) {
        return iface.isSubInterfaceOf(this);
    }

    public String getName() {
        return name;
    }

    public String getSuperClassName() {
        return superClassName;
    }

    public List<String> getInterfaceNames() {
        return interfaceNames;
    }

    public RuntimeConstantPool getRuntimeConstantPool() {
        return runtimeConstantPool;
    }

    public List<Field> getFields() {
        return fields;
    }

    public List<Method> getMethods() {
        return methods;
    }

    public ClassLoader getClassLoader() {
        return classLoader;
    }

    public VMClass getSuperClass() {
        return superClass;
    }

    public List<VMClass> getInterfaces() {
        return interfaces;
    }

    public int getInstanceSlotCount() {
        return instanceSlotCount;
    }

    public int getStaticSlotCount() {
        return staticSlotCount;
    }

    public Slots getStaticVars() {
        return staticVars;
    }

    public void setAccessFlags(int accessFlags) {
        this.accessFlags = accessFlags;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setSuperClassName(String superClassName) {
        this.superClassName = superClassName;
    }

    public void setInterfaceNames(List<String> interfaceNames) {
        this.interfaceNames = interfaceNames;
    }

    public void setRuntimeConstantPool(RuntimeConstantPool runtimeConstantPool) {
        this.runtimeConstantPool = runtimeConstantPool;
    }

    public void setFields(List<Field> fields) {
        this.fields = fields;
    }

    public void setMethods(List<Method> methods) {
        this.methods = methods;
    }

    public void setClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    public void setSuperClass(VMClass superClass) {
        this.superClass = superClass;
    }

    public void setInterfaces(List<VMClass> interfaces) {
        this.interfaces = interfaces;
    }

    public void setInstanceSlotCount(int instanceSlotCount) {
        this.instanceSlotCount = instanceSlotCount;
    }

    public void setStaticSlotCount(int staticSlotCount) {
        this.staticSlotCount = staticSlotCount;
    }

    public void setStaticVars(Slots staticVars) {
        this.staticVars = staticVars;
    }

    public com.vmj.classfile.entity.ConstantPool getCp() {
        return cp;
    }

    public void setCp(com.vmj.classfile.entity.ConstantPool cp) {
        this.cp = cp;
    }

    public boolean isInitStarted() {
        return initStarted;
    }

    public void setInitStarted(boolean initStarted) {
        this.initStarted = initStarted;
    }

    public VMObject getjClass() {
        return jClass;
    }

    public void setjClass(VMObject jClass) {
        this.jClass = jClass;
    }

    public boolean isAssignableFrom(VMClass other) {
        VMClass s = other;
        VMClass t = this;

        if(s.equals(t)){
            return true;
        }

        if(!s.isArray()){
            if(!s.isInterface()) {
                // s is class
                if(!t.isInterface()) {
                    // t is not interface
                    return s.isSubClassOf(t);
                } else {
                    // t is interface
                    return s.isImplements(t);
                }
            } else {
                // s is interface
                if(!t.isInterface()) {
                    // t is not interface
                    return t.isJlObject();
                } else {
                    // t is interface
                    return t.isSuperInterfaceOf(s);
                }
            }
        } else {
            // s is array
            if(!t.isArray()) {
                if(!t.isInterface()) {
                    // t is class
                    return t.isJlObject();
                } else {
                    // t is interface
                    return t.isJlCloneable() || t.isJioSerializable();
                }
            } else {
                // t is array
                VMClass sc = s.getComponentClass();
                VMClass tc = t.getComponentClass();
                return sc == tc || tc.isAssignableFrom(sc);
            }
        }
    }

    public String getSourceFile() {
        return sourceFile;
    }

    public void setSourceFile(String sourceFile) {
        this.sourceFile = sourceFile;
    }

    @Override
    public String toString() {
        return "Class{" +
                "name='" + name + '\'' +
                ", superClassName='" + superClassName + '\'' +
                ", interfaceNames=" + interfaceNames +
                '}';
    }
}
